package se.webbzon.boltzmann.game.object.trigger;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import se.webbzon.boltzmann.configuration.Controls;
import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.environment.PlayerEnvironment;
import se.webbzon.boltzmann.game.object.player.AbstractPlayerCharacter;
import se.webbzon.boltzmann.game.sequence.Sequence;
import se.webbzon.boltzmann.game.sequence.SequenceListener;
import se.webbzon.boltzmann.game.sequence.SequenceNode;
import se.webbzon.boltzmann.game.sequence.builder.SequenceBuilder;
import se.webbzon.boltzmann.game.sequence.builder.SequenceBuilderDeflation;
import se.webbzon.boltzmann.game.sequence.builder.SoftSequenceBinding;
import se.webbzon.boltzmann.input.Input;
import se.webbzon.boltzmann.player.Player;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldeditor.DeflatableWorldObject;
import se.webbzon.oschi01.worldeditor.WorldObjectDeflation;
import se.webbzon.oschi01.worldobject.WorldObject;

public class SequenceTrigger extends AbstractTrigger implements DeflatableWorldObject {

	// The name of this deflatable world object
	public static final String worldObjectName = "sequenceTrigger";
	
	// The sequence builder associated with this trigger
	private final SequenceBuilder builder;
	
	// The sequence node associated with this sequence (only available if
	// debugging mode is false)
	private SequenceNode sequenceNode;
	
	// Whatever this sequence trigger is a blocker sequence or not
	private boolean blocking;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new trigger with a given trigger condition and
	 * a new empty sequence builder. **/
	public SequenceTrigger(AbstractEnvironment env, TriggerCondition condition) {
		this(env, condition, new SequenceBuilder(env));
	}
	
	/** Creates a new trigger with a given trigger condition and
	 * a sequence builder. **/
	public SequenceTrigger(	AbstractEnvironment env, TriggerCondition condition, 
							SequenceBuilder builder) {
		this(env, condition, builder, true);
	}
	
	/** Creates a new trigger with a given trigger condition and
	 * a sequence builder. This sequence trigger can block the players
	 * of the game if the parameter "blocking" is set. **/
	public SequenceTrigger(	AbstractEnvironment env, TriggerCondition condition, 
							SequenceBuilder builder, boolean blocking) {
		super(WorldObject.onCreate,env,condition);
		this.builder = builder;
		this.blocking = blocking;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns whatever this sequence trigger is a blocker sequence or not. **/
	public boolean isBlocking() {
		return blocking;
	}
	
	/** Sets whatever this sequence trigger is a blocker sequence or not. **/
	public void setBlocking(boolean blocking) {
		this.blocking = blocking;
	}
	
	/** Returns the sequence builder associated with this 
	 * sequence trigger.**/
	public SequenceBuilder getBuilder() {
		return builder;
	}
	
	@Override public void onCreate(WorldArea world) {
		if (!getEnvironment().isDebugMode())
			sequenceNode = builder.compile(world);
		
		super.onCreate(world);
	}


	@Override public String getName() {
		return worldObjectName;
	}
	
	@Override public WorldObjectDeflation getDeflation() {
		return new SequenceTriggerDeflation(0,this,new SequenceBuilderDeflation(builder));
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Triggers this trigger. **/
	@Override protected final void trigger() {
		Input input = null;
		AbstractEnvironment env = getEnvironment();
		Sequence sequence = sequenceNode.getSequence();
		Map<String,WorldObject> bindings = new HashMap<String,WorldObject>();
		if (env instanceof PlayerEnvironment) {
			// Use existing players in sequence
			PlayerEnvironment penv = (PlayerEnvironment) env;

			for (Player player : penv.getPlayers()) {
				input = player.getInput();
				break;
			}
			
			Collection<AbstractPlayerCharacter> players = getWorld().findInstances(AbstractPlayerCharacter.class);
			if (!players.isEmpty())
				bindings.put(SoftSequenceBinding.PLAYER, players.iterator().next());
			if (blocking)
				sequence.addListener(new SequenceListenerImpl(players));
		} else {
			// Use the default input and no players
			input = Controls.instance().getInput();
		}
		
		sequence.begin(input, sequenceNode, bindings);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of a sequence listener. **/
	private final class SequenceListenerImpl implements SequenceListener {
		
		private final Map<AbstractPlayerCharacter,Object> blockers;
		
		public SequenceListenerImpl(Collection<AbstractPlayerCharacter> players) {
			blockers = new HashMap<AbstractPlayerCharacter,Object>();
			for (AbstractPlayerCharacter player : players)
				blockers.put(player, null);
		}

		@Override public void onSequenceBegin(Sequence sequence) {
			for (AbstractPlayerCharacter player : blockers.keySet().toArray(new AbstractPlayerCharacter[0]))
				blockers.put(player, player.containBlocker());
		}

		@Override public void onSequenceEnd(Sequence sequence) {
			for (Map.Entry<AbstractPlayerCharacter,Object> entry : blockers.entrySet())
				entry.getKey().releaseBlocker(entry.getValue());
		}
		
	}
	
}
